/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.util;

import java.util.ArrayList;
import java.util.List;

import org.jboss.logging.MDC;
import org.openislands.oi.constant.Dict;
import org.openislands.oi.constant.LogTraceType;
import org.openislands.oi.constant.LogType;
import org.openislands.oi.constant.ServiceName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.spi.LocationAwareLogger;

import sun.misc.JavaLangAccess;
import sun.misc.SharedSecrets;

public class CustomerLogger {
	private static final Logger logger = LoggerFactory.getLogger(CustomerLogger.class);

	/**
	 * 全限定类名
	 */
	private static final String FQCN = CustomerLogger.class.getName();
	private static final String EMPTY_STRING = "";
	/**
	 * 默认服务名称
	 */
	private ServiceName defaultServiceName;

	private LogTraceType defaultLogTraceType;
	/**
	 * 默认日志类型
	 */
	private LogType defaultLogType;
	/**
	 * 默认业务日志路径
	 */
	private String defaultLogPath;
	
	private ThreadLocal<ServiceName> tlServiceName = new ThreadLocal<>();
	private ThreadLocal<String> tlLogPath = new ThreadLocal<>();
	private ThreadLocal<LogType> tlLogType = new ThreadLocal<>();
	
	/**
	 * 构建自定义日志对象
	 * 
	 * @param serviceName 服务名称，若为NULL默认为系统属性service-name
	 * @param logType     日志类型，若为NULL默认为LogType.STATS
	 * @param bizLogPaths 业务日志路径，主要用于动态设置任务日志路径，数组第一个值建议传jobId
	 */
	public CustomerLogger(ServiceName serviceName, LogTraceType logTraceType, LogType logType, Object...bizLogPaths) {
		// 如果服务名称不为空，则默认服务名称为日志对象初始化时的服务名称
		this.defaultServiceName = serviceName != null ? serviceName : ServiceName.valueOf(System.getProperty(Dict.SERVER_NAME).toUpperCase());
		this.defaultLogTraceType = logTraceType;
		// 如果日志类型不为空，则默认日志类型为日志对象初始化时的日志类型
		this.defaultLogType = logType != null ? logType : LogType.STATS;
		// 如果业务日志路径不为空，则默认业务日志路径为
		defaultLogPath = getLogPath(bizLogPaths);
	}
	
	/**
	 * 设置动态业务日志路径的相关参数
	 * 
	 * @param logType     日志类型，若为NULL则默认为初始化时设置的日志类型
	 * @param bizLogPaths 业务日志路径，主要用于动态设置任务日志路径，数组第一个值建议传jobId，若不传该参数则默认为初始化时设置的业务路径，若参数为空字符串，则业务日志路径为空
	 */
	public void set(LogType logType, Object...bizLogPaths) {
		set(null, logType, bizLogPaths);
	}
	
	/**
	 * 设置动态业务日志路径的相关参数
	 * 
	 * @param serviceName 服务名称，若为NULL则默认为初始化时设置的服务名称
	 * @param logType     日志类型，若为NULL则默认为初始化时设置的日志类型
	 * @param bizLogPaths 业务日志路径，主要用于动态设置任务日志路径，数组第一个值建议传jobId，若不传该参数则为初始化时设置的业务路径，若参数为空字符串，则业务日志路径为空
	 */
	public void set(ServiceName serviceName, LogType logType, Object...bizLogPaths) {
		this.tlServiceName.set(serviceName);
		this.tlLogPath.set(getLogPath(bizLogPaths));
		this.tlLogType.set(logType);
	}
	
	/**
	 * 设置服务名称
	 *
	 * @param serviceName 服务名称，若为NULL则默认为初始化时设置的服务名称
	 */
	public void setServiceName(ServiceName serviceName) {
		this.tlServiceName.set(serviceName);
	}

	/**
	 * 设置日志类型
	 *
	 * @param logType 日志类型，若为NULL则默认为初始化时设置的日志类型
	 */
	public void setLogType(LogType logType) {
		this.tlLogType.set(logType);
	}

	/**
	 * 设置业务日志路径
	 *
	 * @param bizLogPaths 业务日志路径，主要用于动态设置任务日志路径，数组第一个值建议传jobId，若不传该参数则为初始化时设置的业务路径，若参数为空字符串，则业务日志路径为空
	 */
	public void setBizLogPaths(Object...bizLogPaths) {
		this.tlLogPath.set(getLogPath(bizLogPaths));
	}

	/**
	 * 获取业务日志路径
	 * 
	 * @param isDefault 是否初始化默认路径
	 * @param bizLogPaths 业务日志路径
	 * @return
	 */
	private String getLogPath(Object...bizLogPaths) {
		// 业务日志路径参数为null或者为空数组，则使用默认的业务日志路径
		List<String> logPathList = new ArrayList<>();
		if(bizLogPaths == null || bizLogPaths.length == 0) {
			return EMPTY_STRING;
		}
		// 业务日志路径参数不为空数组，且数组内元素不为空，则使用传入的参数作为日志文件路径
		for(Object logPath : bizLogPaths) {
			if(logPath != null) {
				String temp = String.valueOf(logPath).trim();
				if(temp.length() > 0) {
					logPathList.add(temp);
				}
			}
		}
		return String.join("/", logPathList);
	}
	
	/**
	 * 打印INFO级别日志
	 * 
	 * @param message 待输出日志内容
	 */
	public void info(String message) {
		this.log(2, LocationAwareLogger.INFO_INT, null, message, null);
	}

	public void info(String traceId, String message) {
		this.log(2, LocationAwareLogger.INFO_INT, traceId, message, null);
	}

	/**
	 * 打印INFO级别格式化日志
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param formatParams 格式化日志参数 
	 */
	public void info(String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.INFO_INT, null, formatMsg, null, formatParams);
	}

	public void info(String traceId, String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.INFO_INT, traceId, formatMsg, null, formatParams);
	}

	/**
	 * 打印INFO级别格式化日志及异常信息栈
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param throwable    异常信息
	 * @param formatParams 格式化日志参数 
	 */
	public void info(String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.INFO_INT, null, formatMsg, throwable, formatParams);
	}

	public void info(String traceId, String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.INFO_INT, traceId, formatMsg, throwable, formatParams);
	}

	/**
	 * 打印DEBUG级别日志
	 * 
	 * @param message 待输出日志内容
	 */
	public void debug(String message) {
		this.log(2, LocationAwareLogger.DEBUG_INT, null, message, null);
	}

	public void debug(String traceId, String message) {
		this.log(2, LocationAwareLogger.DEBUG_INT, traceId, message, null);
	}

	/**
	 * 打印DEBUG级别格式化日志
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param formatParams 格式化日志参数 
	 */
	public void debug(String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.DEBUG_INT, null, formatMsg, null, formatParams);
	}

	public void debug(String traceId, String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.DEBUG_INT, traceId, formatMsg, null, formatParams);
	}

	/**
	 * 打印DEBUG级别格式化日志及异常信息栈
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param throwable    异常信息
	 * @param formatParams 格式化日志参数 
	 */
	public void debug(String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.DEBUG_INT, null, formatMsg, throwable, formatParams);
	}

	public void debug(String traceId, String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.DEBUG_INT, traceId, formatMsg, throwable, formatParams);
	}

	/**
	 * 打印ERROR级别日志
	 * 
	 * @param message 待输出日志内容
	 */
	public void error(String message) {
		this.log(2, LocationAwareLogger.ERROR_INT, null, message, null);
	}

	public void error(String traceId, String message) {
		this.log(2, LocationAwareLogger.ERROR_INT, traceId, message, null);
	}

	/**
	 * 打印ERROR级别格式化日志
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param formatParams 格式化日志参数 
	 */
	public void error(String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.ERROR_INT, null, formatMsg, null, formatParams);
	}

	public void error(String traceId, String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.ERROR_INT, traceId, formatMsg, null, formatParams);
	}

	/**
	 * 打印ERROR级别格式化日志及异常信息栈
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param throwable    异常信息
	 * @param formatParams 格式化日志参数 
	 */
	public void error(String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.ERROR_INT, null, formatMsg, throwable, formatParams);
	}

	public void error(String traceId, String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.ERROR_INT, traceId, formatMsg, throwable, formatParams);
	}

	/**
	 * 打印WARN级别日志
	 * 
	 * @param message 待输出日志内容
	 */
	public void warn(String message) {
		this.log(2, LocationAwareLogger.WARN_INT, null, message, null);
	}

	public void warn(String traceId, String message) {
		this.log(2, LocationAwareLogger.WARN_INT, traceId, message, null);
	}

	/**
	 * 打印WARN级别格式化日志
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param formatParams 格式化日志参数 
	 */
	public void warn(String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.WARN_INT, null, formatMsg, null, formatParams);
	}

	public void warn(String traceId, String formatMsg, Object...formatParams) {
		this.log(2, LocationAwareLogger.WARN_INT, traceId, formatMsg, null, formatParams);
	}

	/**
	 * 打印WARN级别格式化日志及异常信息栈
	 * 
	 * @param formatMsg    待输出的格式化日志内容
	 * @param throwable    异常信息
	 * @param formatParams 格式化日志参数 
	 */
	public void warn(String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.WARN_INT, null, formatMsg, throwable, formatParams);
	}

	public void warn(String traceId, String formatMsg, Throwable throwable, Object...formatParams) {
		this.log(2, LocationAwareLogger.WARN_INT, traceId, formatMsg, throwable, formatParams);
	}

	/**
	 * 输出格式化日志消息及异常信息栈到指定业务日志路径的日志文件中
	 *
	 * @param stackDepth  方法栈指定深度
	 * @param level       日志级别
	 * @param formatMsg    待格式化消息
	 * @param formatParams 格式化参数
	 * @param throwable    异常信息对象
	 */
	private void log(int stackDepth, int level, String traceId, String formatMsg, Throwable throwable, Object...formatParams) {
		/* 设置日志文件的业务路径，日志路径为：
		 * 1）服务日志：{log_home}/{service_name}/{logType}.log
		 * 2）任务日志：{log_home}/{service_name}/{bizLogPaths}/{logType}.log
		 * 3）级别日志(服务)：{log_home}/{service_name}/{error,warn,info,debug}.log
		 * 4）级别日志(任务)：{log_home}/{service_name}/{bizLogPaths}/{error,warn,info,debug}.log
		 */
		ServiceName serviceName = tlServiceName.get();
		if(serviceName == null) {
			serviceName = defaultServiceName;
		}
		String logDir;
		if (this.defaultLogTraceType != null){
			logDir = this.defaultLogTraceType.name().toLowerCase();
		}else {
			logDir = serviceName.name().toLowerCase();
		}
		String logSource;
		if (!StringUtils.isEmpty(traceId)){
			logDir = String.join("/", logDir, traceId);
			logSource = String.join(":", serviceName.name().toLowerCase(), traceId);
		}else {
			logSource = serviceName.name().toLowerCase();
		}
		String logPath = tlLogPath.get();
		if(logPath == null || logPath.isEmpty()) {
			logPath = defaultLogPath;
		}
		if (logPath == null || logPath.isEmpty()){
			logPath = logDir;
		}else{
			logPath = String.join("/", logDir, logPath);
		}
		MDC.put("relative_log_path", logPath);
		LogType logType = tlLogType.get();
		if(logType == null) {
			logType = defaultLogType;
		}
		MDC.put("relative_biz_log_path", String.join("/", logPath, logType.name().toLowerCase()));
		MDC.put("logSource", logSource);
		getLocationAwareLogger(stackDepth).log(null, FQCN, level, formatMsg, formatParams, throwable);
		MDC.remove("relative_log_path");
		MDC.remove("relative_biz_log_path");
		MDC.remove("logSource");
	}
	
	private LocationAwareLogger getLocationAwareLogger(final int stackDepth) {
		// 通过堆栈信息获取调用当前方法的类名和方法名
		JavaLangAccess access = SharedSecrets.getJavaLangAccess();
		Throwable throwable = new Throwable();
		StackTraceElement frame = access.getStackTraceElement(throwable, stackDepth);
		return (LocationAwareLogger) LoggerFactory.getLogger(frame.getClassName());
	}
}
